#include "xh_coroutine.hpp"

#if defined(_MSC_VER) && _MSC_VER < 1900
	#define co_thread_local __declspec(thread)
#else 
	#define co_thread_local thread_local
#endif

// ==========================================================================================

co_thread_local CODispatcher* g_dispatcher = nullptr;

// ==========================================================================================

extern CODispatcher* get_current_codispatcher()
{
	if (g_dispatcher == nullptr)
		CODispatcher::startup_current_thread();
	return g_dispatcher;
}

COTask::COTask(CODispatcher* dispatcher, std::size_t stack_size, const std::function<void()>& func)
	: m_dispatcher(dispatcher)
	, m_stack_size(stack_size)
	, m_status(STATUS_NONE)
	, m_func(func)
{
	m_stack = (char*)std::malloc(stack_size);
	m_context = boost::context::make_fcontext(m_stack + m_stack_size, m_stack_size, &COTask::entry);
}

void COTask::handle_finish()
{
	this->m_status = STATUS_FINISH;
	this->m_dispatcher->yield();
}

COTask::~COTask()
{
	std::free(m_stack);
}

void COTask::entry(intptr_t v)
{
	auto t = (COTask*)v;
	t->run();
	t->handle_finish();
}

// ==========================================================================================

CODispatcher::CODispatcher()

	: m_current(nullptr)
	, m_main_context(nullptr)
{

}


CODispatcher::~CODispatcher()
{
	cleanup();
}

COTask* CODispatcher::create(const std::function<void()>& func, std::size_t stack_size /*= 81920*/)
{
	auto t = new COTask(this, stack_size, func);
	this->m_tasks.insert(t);
	this->resume(t);
	return t;
}

void CODispatcher::resume(COTask* t)
{
	if (t->m_status == COTask::STATUS_NONE || t->m_status == COTask::STATUS_RUNNING)
	{
		t->m_status = COTask::STATUS_WAIT;
		m_ready_tasks.push_back(t);
	}
}

void CODispatcher::yield()
{
	if (m_current->m_status == COTask::STATUS_FINISH)
		this->m_finished_tasks.push_back(m_current);
	else
		this->resume(m_current);
	COTask* current = nullptr;
	std::swap(current, m_current);
	boost::context::jump_fcontext(&current->m_context, m_main_context, 0);
}

void CODispatcher::suspend()
{
	COTask* current = nullptr;
	std::swap(current, m_current);
	current->m_status = COTask::STATUS_NONE;
	boost::context::jump_fcontext(&current->m_context, m_main_context, 0);
}

void CODispatcher::loop_once()
{
	while (!m_ready_tasks.empty())
	{
		m_current = m_ready_tasks.front();
		m_current->m_status = COTask::STATUS_RUNNING;
		m_ready_tasks.pop_front();
		boost::context::jump_fcontext(&m_main_context, m_current->m_context, (intptr_t)m_current);
	}

	while (!m_finished_tasks.empty())
	{
		auto t = m_finished_tasks.front();
		m_finished_tasks.pop_front();
		m_tasks.erase(t);
		delete t;
	}
}

void CODispatcher::loop(unsigned int sleep_microsecs)
{
	while (!m_tasks.empty())
	{
		this->loop_once();
		std::this_thread::sleep_for(std::chrono::microseconds(sleep_microsecs));
	}
}

void CODispatcher::cleanup()
{
	for (auto& v : m_tasks)
		delete v;
	m_tasks.clear();
	m_finished_tasks.clear();
	m_ready_tasks.clear();
}

void CODispatcher::startup_current_thread()
{
	if (g_dispatcher == nullptr)
		g_dispatcher = new CODispatcher();
}

void CODispatcher::cleanup_current_thread(bool release_dispatcher/* = true*/)
{
	g_dispatcher->cleanup();
	if (release_dispatcher)
	{
		delete g_dispatcher;
		g_dispatcher = nullptr;
	}
}


extern void _co_select(COSelector& selector) {}